import SetupEnv from './common/setup-env.mdx';

# 使用 YAML 格式的自动化脚本

在大多数情况下，开发者编写自动化脚本只是为了执行一些简单流程，比如检查某些内容是否出现，或者验证某个关键用户路径是否可用。此时维护一个大型测试项目会显得毫无必要。

⁠Midscene 提供了一种基于 `.yaml` 文件的自动化测试方法，这有助于你专注于编写流程，而不是测试框架。

这里有一个示例，通过阅读它的内容，你应该已经理解了它的工作原理。

```yaml
web:
  url: https://www.bing.com

tasks:
  - name: 搜索天气
    flow:
      - ai: 搜索 "今日天气"
      - sleep: 3000

  - name: 检查结果
    flow:
      - aiAssert: 结果中展示了天气信息
```

:::info 样例项目

你可以在这里找到使用 YAML 脚本做自动化的样例项目

- [Web](https://github.com/web-infra-dev/midscene-example/tree/main/yaml-scripts-demo)
- [Android](https://github.com/web-infra-dev/midscene-example/tree/main/android/yaml-scripts-demo)

:::

<SetupEnv />

或使用当前命令运行目录下的 `.env` 文件存储配置，Midscene 命令行工具在运行 yaml 脚本时会自动加载它

```ini filename=.env
OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz"
```

## 使用命令行工具

全局安装 `@midscene/cli`

```bash
npm i -g @midscene/cli
# 或在项目中安装
npm i @midscene/cli --save-dev
```

编写一个名为 `bing-search.yaml` 的文件来驱动 web 浏览器的自动化任务

```yaml
web:
  url: https://www.bing.com

tasks:
  - name: 搜索天气
    flow:
      - ai: 搜索 "今日天气"
      - sleep: 3000
      - aiAssert: 结果显示天气信息
```

或者驱动安卓设备的自动化任务（需要使用 adb 连接安卓设备）

```yaml
android:
  # launch: https://www.bing.com
  deviceId: s4ey59

tasks:
  - name: 搜索天气
    flow:
      - ai: 打开浏览器并导航到 bing.com
      - ai: 搜索 "今日天气"
      - sleep: 3000
      - aiAssert: 结果显示天气信息
```

或者驱动 iOS 设备的自动化任务（需要配置 WebDriverAgent）

```yaml
ios:
  # launch: com.apple.mobilesafari
  wdaPort: 8100

tasks:
  - name: 搜索天气
    flow:
      - ai: 打开浏览器并导航到 bing.com
      - ai: 搜索 "今日天气"
      - sleep: 3000
      - aiAssert: 结果显示天气信息
```

运行脚本

```bash
midscene ./bing-search.yaml
# 或者如果你在项目中安装了 midscene
npx midscene ./bing-search.yaml
```

你将会看到脚本的执行进度和可视化运行报告文件。

## 脚本文件结构

脚本文件使用 YAML 格式来描述自动化任务。它定义了要操作的目标（如网页或安卓应用）以及一系列要执行的步骤。

一个标准的 `.yaml` 脚本文件包含 `web`、`android` 或 `ios` 部分配置环境，以及一个 `tasks` 部分来定义自动化任务。

```yaml
web:
  url: https://www.bing.com

# tasks 部分定义了要执行的一系列步骤
tasks:
  - name: 搜索天气
    flow:
      - ai: 搜索 "今日天气"
      - sleep: 3000
      - aiAssert: 结果显示天气信息
```

### `web` 部分

```yaml
web:
  # 访问的 URL，必填。如果提供了 `serve` 参数，则提供相对路径
  url: <url>

  # 在本地路径下启动一个静态服务，可选
  serve: <root-directory>

  # 浏览器 UA，可选
  userAgent: <ua>

  # 浏览器视口宽度，可选，默认 1280
  viewportWidth: <width>

  # 浏览器视口高度，可选，默认 960
  viewportHeight: <height>

  # 浏览器设备像素比，可选，默认 1
  deviceScaleFactor: <scale>

  # JSON 格式的浏览器 Cookie 文件路径，可选
  cookie: <path-to-cookie-file>

  # 等待网络空闲的策略，可选
  waitForNetworkIdle:
    # 等待超时时间，可选，默认 2000ms
    timeout: <ms>
    # 是否在等待超时后继续，可选，默认 true
    continueOnNetworkIdleError: <boolean>

  # 输出 aiQuery/aiAssert 结果的 JSON 文件路径，可选
  output: <path-to-output-file>

  # 是否保存日志内容到 JSON 文件，可选，默认 `false`。如果为 true，保存到 `unstableLogContent.json` 文件中。如果为字符串，则保存到该字符串指定的路径中。日志内容的结构可能会在未来发生变化。
  unstableLogContent: <boolean | path-to-unstable-log-file>

  # 是否限制页面在当前 tab 打开，可选，默认 true
  forceSameTabNavigation: <boolean>

  # 桥接模式，可选，默认 false，可以为 'newTabWithUrl' 或 'currentTab'。更多详情请参阅后文
  bridgeMode: false | 'newTabWithUrl' | 'currentTab'

  # 是否在桥接断开时关闭新创建的标签页，可选，默认 false
  closeNewTabsAfterDisconnect: <boolean>

  # 是否忽略 HTTPS 证书错误，可选，默认 false
  acceptInsecureCerts: <boolean>
```

### 全局 agent 配置

如需使用 `aiActionContext` 参数，可以通过全局的 `agent` 配置来设置：

```yaml
# 全局 AI agent 配置
agent:
  # 在调用 aiAction 时发送给 AI 模型的背景知识，可选
  aiActionContext: <string>
```

:::tip aiActionContext 配置说明

- **适用环境**：Web、iOS 和 Android 环境都可以通过全局的 `agent` 配置来设置 `aiActionContext`
- **作用**：为 AI 模型提供背景知识，例如处理弹窗、业务介绍等常见场景

:::

#### 使用示例

```yaml
# 全局 agent 配置，适用于所有环境
agent:
  aiActionContext: "如果出现弹窗，点击同意。如果出现登录页面，跳过它。"

# iOS 环境配置
ios:
  launch: https://www.bing.com
  wdaPort: 8100

# 或 Android 环境配置
android:
  deviceId: s4ey59
  launch: https://www.bing.com

tasks:
  - name: 搜索天气
    flow:
      - ai: 搜索 "今日天气"
      - aiAssert: 结果显示天气信息
```

### `android` 部分

```yaml
android:
  # 设备 ID，可选，默认使用第一个连接的设备
  deviceId: <device-id>

  # 启动 URL，可选，默认使用设备当前页面
  launch: <url>

  # 输出 aiQuery/aiAssert 结果的 JSON 文件路径，可选
  output: <path-to-output-file>
```

### `ios` 部分

```yaml
ios:
  # WebDriverAgent 端口，可选，默认 8100
  wdaPort: <port>

  # WebDriverAgent 主机地址，可选，默认 localhost
  wdaHost: <host>

  # 是否自动关闭键盘，可选，默认 false
  autoDismissKeyboard: <boolean>

  # 启动 URL 或应用包名，可选，默认使用设备当前页面
  launch: <url-or-bundle-id>

  # 输出 aiQuery/aiAssert 结果的 JSON 文件路径，可选
  output: <path-to-output-file>

  # 是否保存日志内容到 JSON 文件，可选，默认 `false`。如果为 true，保存到 `unstableLogContent.json` 文件中。如果为字符串，则保存到该字符串指定的路径中。日志内容的结构可能会在未来发生变化。
  unstableLogContent: <boolean | path-to-unstable-log-file>
```

### `tasks` 部分

`tasks` 部分是一个数组，定义了脚本执行的步骤。记得在每个步骤前添加 `-` 符号，表明这些步骤是个数组。

`flow` 部分的接口与 [API](./api.html) 几乎相同，除了一些参数的嵌套层级。

```yaml
tasks:
  - name: <name>
    continueOnError: <boolean> # 可选，错误时是否继续执行下一个任务，默认 false
    flow:
      # 自动规划(Auto Planning, .ai)
      # ----------------

      # 执行一个交互，`ai` 是 `aiAction` 的简写方式
      - ai: <prompt>
        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 这种用法与 `ai` 相同
      - aiAction: <prompt>
        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 即时操作(Instant Action, .aiTap, .aiHover, .aiInput, .aiKeyboardPress, .aiScroll)
      # ----------------

      # 点击一个元素，用 prompt 描述元素位置
      - aiTap: <prompt>
        deepThink: <boolean> # 可选，是否使用深度思考（deepThink）来精确定位元素。默认值为 False
        xpath: <xpath> # 可选，目标元素的 xpath 路径，用于执行当前操作。如果提供了这个 xpath，Midscene 会优先使用该 xpath 来找到元素，然后依次使用缓存和 AI 模型。默认值为空
        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 鼠标悬停一个元素，用 prompt 描述元素位置
      - aiHover: <prompt>
        deepThink: <boolean> # 可选，是否使用深度思考（deepThink）来精确定位元素。默认值为 False
        xpath: <xpath> # 可选，目标元素的 xpath 路径，用于执行当前操作。如果提供了这个 xpath，Midscene 会优先使用该 xpath 来找到元素，然后依次使用缓存和 AI 模型。默认值为空

        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 输入文本到一个元素，用 prompt 描述元素位置
      - aiInput: <输入框的最终文本内容>
        locate: <prompt>
        deepThink: <boolean> # 可选，是否使用深度思考（deepThink）来精确定位元素。默认值为 False
        xpath: <xpath> # 可选，目标元素的 xpath 路径，用于执行当前操作。如果提供了这个 xpath，Midscene 会优先使用该 xpath 来找到元素，然后依次使用缓存和 AI 模型。默认值为空
        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 在元素上按下某个按键（如 Enter，Tab，Escape 等），用 prompt 描述元素位置
      - aiKeyboardPress: <按键>
        locate: <prompt>
        deepThink: <boolean> # 可选，是否使用深度思考（deepThink）来精确定位元素。默认值为 False
        xpath: <xpath> # 可选，目标元素的 xpath 路径，用于执行当前操作。如果提供了这个 xpath，Midscene 会优先使用该 xpath 来找到元素，然后依次使用缓存和 AI 模型。默认值为空

        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 全局滚动，或滚动 prompt 描述的元素
      - aiScroll:
        direction: 'up' # 或 'down' | 'left' | 'right'
        scrollType: 'once' # 或 'untilTop' | 'untilBottom' | 'untilLeft' | 'untilRight'
        distance: <number> # 可选，滚动距离，单位为像素
        locate: <prompt> # 可选，执行滚动的元素
        deepThink: <boolean> # 可选，是否使用深度思考（deepThink）来精确定位元素。默认值为 False
        xpath: <xpath> # 可选，目标元素的 xpath 路径，用于执行当前操作。如果提供了这个 xpath，Midscene 会优先使用该 xpath 来找到元素，然后依次使用缓存和 AI 模型。默认值为空

        cacheable: <boolean> # 可选，当启用 [缓存功能](./caching.mdx) 时，是否允许缓存当前 API 调用结果。默认值为 True

      # 在报告文件中记录当前截图，并添加描述
      - logScreenshot: <title> # 可选，截图的标题，如果未提供，则标题为 'untitled'
        content: <content> # 可选，截图的描述

      # 数据提取
      # ----------------

      # 执行一个查询，返回一个 JSON 对象
      - aiQuery: <prompt> # 记得在提示词中描述输出结果的格式
        name: <name> # 查询结果在 JSON 输出中的 key

      # 更多 API
      # ----------------

      # 等待某个条件满足，并设置超时时间(ms，可选，默认 30000)
      - aiWaitFor: <prompt>
        timeout: <ms>

      # 执行一个断言
      - aiAssert: <prompt>
        errorMessage: <error-message> # 可选，当断言失败时打印的错误信息。
        name: <name> # 可选，给断言一个名称，会在 JSON 输出中作为 key 使用

      # 等待一定时间
      - sleep: <ms>

      # 在 web 页面上下文中执行一段 JavaScript 代码
      - javascript: <javascript>
        name: <name> # 可选，给返回值一个名称，会在 JSON 输出中作为 key 使用

  - name: <name>
    flow:
      # ...
```


#### 使用图像提示

对于支持在提示词中附带图像的步骤（参见 [API 参考](./api.html#prompting-with-images)），可以把提示词改写为对象，并通过设置 `images` 字段（一个包含 `name` 和 `url` 的对象数组）来附加图像。该对象包含以下字段：

- `prompt`：发送给模型的文本描述。
- `images`（可选）：提示词引用的参考图像，每一项需要提供 `name` 和 `url`。
- `convertHttpImage2Base64`（可选）：在图片链接无法公开访问时，将 HTTP 链接转换为 Base64 再发送给模型。

图片 URL 可以是本地路径、Base64 字符串或远程链接。如果图片链接无法被模型访问，请设置 `convertHttpImage2Base64: true`，Midscene 会将图像下载后以 Base64 字符串的形式发送给模型。

对于 `aiTap`、`aiHover`、`aiDoubleClick`、`aiRightClick` 等交互操作，请把文本和图像配置写在 `locate` 字段中。

```yaml
tasks:
  - name: 校验品牌一致性
    flow:
      - aiHover:
          locate:
            prompt: 将鼠标移动到包含 GitHub 标志的区域。
            images:
              - name: GitHub 标志
                url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png
            convertHttpImage2Base64: true

      - aiTap:
          locate:
            prompt: 点击包含 GitHub 标志的区域。
            images:
              - name: GitHub 标志
                url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png
            convertHttpImage2Base64: true
```

对于视觉问答类步骤，例如 `aiAsk`、`aiQuery`、`aiBoolean`、`aiNumber`、`aiString`、`aiAssert`，可以直接设置 `prompt` 和 `images` 字段。

```yaml
tasks:
  - name: 校验品牌一致性
    flow:
      - aiAssert:
          prompt: 判断页面上是否出现该图像。
          images:
            - name: 目标标志
              url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png
          convertHttpImage2Base64: true
```


## 命令行工具的高级用法

`@midscene/cli` 提供了灵活的方式来运行你的自动化脚本。

### 运行一个或多个脚本

你可以直接向 `midscene` 命令传递一个 `.yaml` 脚本文件或使用通配符模式来匹配多个 `.yaml`。这是 `--files` 参数的简写方式。

```bash
# 运行单个脚本
midscene ./bing-search.yaml

# 使用通配符模式运行所有匹配的脚本
midscene './scripts/**/*.yaml'
```

### 命令行选项

命令行工具提供了一些选项来控制脚本的执行行为。

- `--files <file1> <file2> ...`: 指定要执行的脚本文件列表，文件将按顺序执行。支持通配符模式，遵循 [glob](https://www.npmjs.com/package/glob) 支持的语法。
- `--concurrent <number>`: 设置并发执行的数量。默认为 `1`。
- `--continue-on-error`: 如果设置了此选项，即使有脚本文件执行失败，也会继续运行余下的脚本文件。默认关闭。
- `--share-browser-context`: 在所有脚本之间共享同一个浏览器上下文（例如 Cookies 和 `localStorage`）。这对于需要登录状态的连续测试非常有用，默认关闭。
- `--summary <filename>`: 指定生成的 JSON 格式汇总报告文件的路径。
- `--headed`: 在有图形界面的浏览器中运行脚本，而不是在无头模式下。
- `--keep-window`: 脚本执行结束后保持浏览器窗口打开。此选项会自动启用 `--headed` 参数。
- `--config <filename>`: 指定配置文件，配置文件中的参数将作为命令行参数的默认值。
- `--web.userAgent <ua>`: 设置浏览器 UA，这将覆盖所有脚本文件中的 `web.userAgent` 参数。
- `--web.viewportWidth <width>`: 设置浏览器视口宽度，这将覆盖所有脚本文件中的 `web.viewportWidth` 参数。
- `--web.viewportHeight <height>`: 设置浏览器视口高度，这将覆盖所有脚本文件中的 `web.viewportHeight` 参数。
- `--android.deviceId <device-id>`: 设置安卓设备 ID，这将覆盖所有脚本文件中的 `android.deviceId` 参数。
- `--ios.wdaPort <port>`: 设置 WebDriverAgent 端口，这将覆盖所有脚本文件中的 `ios.wdaPort` 参数。
- `--ios.wdaHost <host>`: 设置 WebDriverAgent 主机地址，这将覆盖所有脚本文件中的 `ios.wdaHost` 参数。
- `--dotenv-debug`: 设置 dotenv 的 debug 日志，默认关闭。
- `--dotenv-override`: 设置 dotenv 是否覆盖同名的全局环境变量，默认关闭。

举例：

使用 `--files` 参数来指定文件顺序，并行执行

```bash
midscene --files ./login.yaml ./buy/*.yaml ./checkout.yaml
```

以 4 个并发数运行所有脚本，并在任一文件出错时继续

```bash
midscene --files './scripts/**/*.yaml' --concurrent 4 --continue-on-error
```

### 以文件形式编写命令行参数

你可以编写 YAML 格式的配置文件，然后通过 `--config` 来引用它。调用命令行工具时，命令行参数的优先级高于配置文件。

```yaml
files:
  - './scripts/login.yaml'
  - './scripts/search.yaml'
  - './scripts/**/*.yaml'

concurrent: 4
continueOnError: true
shareBrowserContext: true
```

使用方法：

```bash
midscene --config ./config.yaml
```

## 更多特性

### 在 `.yaml` 文件中使用环境变量

你可以在 `.yaml` 文件中使用环境变量，通过 `${variable-name}` 的方式。

例如，如果你有一个 `.env` 文件，内容如下：

```ini filename=.env
topic=weather today
```

你可以在 `.yaml` 文件中使用环境变量，如下所示：

```yaml
#...
- ai: type ${topic} in input box
#...
```

### 运行在有界面(Headed)模式下

> 仅 `web` 场景下支持

'headed' 模式意味着浏览器窗口是可见的。默认情况下，脚本会在无界面模式下运行。

如果你想运行在有界面模式下，你可以使用 `--headed` 选项。此外，如果你想在脚本运行结束后保持浏览器窗口打开，你可以使用 `--keep-window` 选项。`--keep-window` 选项会自动开启 `--headed` 模式。

headed 模式会消耗更多资源，所以建议你仅在本地使用。

```bash
# 运行在有界面模式下
midscene /path/to/yaml --headed

# 运行在有界面模式下，并在结束后保持浏览器窗口打开
midscene /path/to/yaml --keep-window
```

### 使用桥接模式

> 仅 `web` 场景下支持

通过使用桥接模式，你可以利用 YAML 脚本在已有的桌面浏览器上执行自动化。这对于需要复用 Cookies、插件和页面状态，或者需要人工与自动化脚本交互的情况非常有用。

使用桥接模式，你需要先安装 Chrome 扩展，然后在 `target` 部分使用以下配置：

```diff
web:
  url: https://www.bing.com
+ bridgeMode: newTabWithUrl
```

请参阅 [通过 Chrome 扩展桥接模式](./bridge-mode-by-chrome-extension) 了解更多详细信息。

### 使用 JavaScript 运行 YAML 脚本

你也可以使用 JavaScript 运行 YAML 脚本，调用 Agent 上的 [`runYaml`](./api.html#runyaml) 方法即可。注意，这种方法只会执行 YAML 脚本中的 `tasks` 部分。

### 分析命令行工具的运行结果

执行完成后，会在输出目录中生成以下文件：

- 由 `--summary` 选项指定的文件路径（默认是 `index.json`），包含所有文件的执行状态和统计信息
- 各个 YAML 文件的独立执行结果（JSON 格式）
- 各个文件的可视化报告（HTML 格式）

## 配置 dotenv 的默认行为

Midscene 使用 [`dotenv`](https://github.com/motdotla/dotenv) 加载 `.env` 文件中的环境变量。

### 关闭 dotenv 的 debug 日志

默认情况下，Midscene 会打印 dotenv 的 debug 信息，如果你不想看到这些信息，你可以使用 `--dotenv-debug` 选项关闭。

```bash
midscene /path/to/yaml --dotenv-debug=false
```

### 使用 .env 中的环境变量覆盖同名的全局环境变量

默认情况下，`dotenv` 不会覆盖`.env` 文件中同名的全局环境变量。如果希望覆盖，你可以使用 `--dotenv-override` 选项。

```bash
midscene /path/to/yaml --dotenv-override=true
```

## FAQ

**如何从 Chrome 中获取 JSON 格式的 Cookies？**

你可以使用这个 [Chrome 扩展](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) 来导出 Cookies 为 JSON 格式。

**如何打开 dotenv 的 debug 日志？**

Midscene 使用 `dotenv` 加载 `.env` 文件中的环境变量。你可以使用 `--dotenv-debug` 选项来打开 dotenv 的 debug 日志。

```bash
midscene /path/to/yaml --dotenv-debug=true
```

## 更多

你可能还想了解 [提示词技巧](./prompting-tips)
